// create closure
(function($) {
    //Textbox selection utils
    $.fn.extend({
        gettextselection: function() {
			var e = this.jquery ? this[0] : this;
			return (
				/* mozilla / dom 3.0 */
				('selectionStart' in e && function() {
					return { start: e.selectionStart, end: e.selectionEnd };
				}) ||

				/* exploder */
				(document.selection && function() {

					e.focus();

					var r = document.selection.createRange();
					if (r == null) {
						return { start: 0, end: e.value.length }
					}

					var re = e.createTextRange();
					var rc = re.duplicate();
					re.moveToBookmark(r.getBookmark());
					rc.setEndPoint('EndToStart', re);

					return { start: rc.text.length, end: rc.text.length + r.text.length };
				}) ||

				/* browser not supported */
				function() {
					return { start: 0, end: e.value.length };
				}

			)();
        },
        settextselection: function(start, end) {
    		return this.each(function() {
                var o = this;
                if(o.setSelectionRange) {     /* DOM */
                    setTimeout(function() {
                        if (end) {
                            o.setSelectionRange(start,end);
                        } else {
                            o.setSelectionRange(start,start);                    
                        }
                    }, 2);
                } else if(o.createTextRange) {    /* IE */
                    var r = o.createTextRange();
                    r.moveStart('character', start);
                    if (end) r.moveEnd('character', end);
                    r.select();
                }
            });
        }
    });
    
    $.fn.extend({
    	tokentextbox: function(passed_options) {
    		instance_opts = $.extend({}, $.fn.tokentextbox.defaults, passed_options);
    		
    		var text_measurer = $("body").find("div #_growmeasure");
            if (text_measurer.length==0) {
                text_measurer = $('<div style="position:absolute; left:-5000px"><span id="_growmeasure" ">^_^</span></div>');       
                $("body").append(text_measurer);
            }

    		var temporary_text_input = $("body").find("div #_temporary_text_input");
            if (temporary_text_input.length==0) {
                temporary_text_input = $('<div style="position:absolute; left:-5000px"><input type="text" id="_temporary_text_input" "></input></div>');       
                $("body").append(temporary_text_input);
            }

    		return this.each(function() {
    		    $this = $(this);
    		    var o = $.meta ? $.extend({}, instance_opts, $this.data()) : instance_opts;
    			new $.fn.TokenTextbox($this, o);
    		});
    	}
    });
    
    //A class for holding the tokenised textbox selector
    $.fn.TokenTextbox = function(original_textbox, options) {
        //Initialise the tokentextbox object
        
        //private functions (must have var)
        var newfaketextbox = function(oldtextbox, x, y) {
            var ntb = $('<div class="tokentextboxframe"></div>');
            ntb.css({
               "width": oldtextbox.width(),
               "padding-top": oldtextbox.css("padding-top"),
               "padding-right": oldtextbox.css("padding-right"),
               "padding-bottom": oldtextbox.css("padding-bottom"),
               "padding-left": oldtextbox.css("padding-left"),
               "margin-top": oldtextbox.css("margin-top"),
               "margin-right": oldtextbox.css("margin-right"),
               "margin-bottom": oldtextbox.css("margin-bottom"),
               "margin-left": oldtextbox.css("margin-left"),
               "background-color": oldtextbox.css("background-color"),
               "font-family": oldtextbox.css("font-family"),
               "float": oldtextbox.css("float"),
               "font-size": oldtextbox.css("font-size"),
               "cursor": "text"
            });

            return ntb
        };    

        var token_html = function(token_text) {
            return '<span class="token">'+$.trim(token_text)+'<span class="x">x</span></span>';
        };

        var tokens_html = function() {
            var strn = original_textbox.val();
            var tokens = strn.split(options.separators);
            var tokenElements = "";
            for (var i=0; i<tokens.length; i++) {
                if ($.fn.tokentextbox.token_is_valid(tokens[i])) {
                   tokenElements += token_html(tokens[i]);
                }
            };
            return tokenElements;
        };

        var init_autocomplete = function() {
            var ac_options = $.extend({}, options.autocomplete_options);
            token_textbox.parentautocomplete('', ac_options);
        };

        var show_token_textbox = function(event) {
            token_textbox = fake_textbox.find("input");
            if (token_textbox.length == 0) { // If it doesn't exist, create it.
                token_textbox = $('<input type="text" style="border: 0; padding: 0 0 2px 0; margin: 1px 0 -1px 0; width:'+options.token_textbox_pad+'px; background-color: transparent" />');
                token_textbox.css({
                    "font-family": original_textbox.css("font-family"),
                    "font-size": original_textbox.css("font-size")
                });
                fake_textbox.append(token_textbox);
                init_autocomplete();
            } else { //Otherwise just show it.
                token_textbox.css("display", "inline");
            }
            
            token_textbox.bind("keyup", token_textbox_keyup);
            token_textbox.bind("keypress", token_textbox_keypress);
            token_textbox.bind(($.browser.opera ? "keypress" : "keydown"), token_textbox_keypress);
            token_textbox.bind("blur", token_textbox_handle_blur);
            token_textbox.focus();
        };
        
        var put_textbox_at_end = function() {
            token_textbox = fake_textbox.find("input");
            token_textbox.unautocomplete();
            token_textbox.appendTo(fake_textbox);
            init_autocomplete(); //TODO: bug with autocomplete menus persisting
            setTimeout(show_token_textbox, 2);
        }
        
        var put_textbox_after = function(token) {
            token_textbox.unautocomplete();
            token_textbox.insertAfter(token);
            init_autocomplete();
            setTimeout(show_token_textbox, 2);
        };
        
        var put_textbox_before = function(token) {
            token_textbox.unautocomplete();
            token_textbox.insertBefore(token);
            init_autocomplete();
            setTimeout(show_token_textbox, 2);
        };

        var hide_token_textbox = function() {
            token_textbox.css("display", "none");
        };
        
        var token_textbox_keypress = function(event) {
            var tb = token_textbox;
            var key = event.keyCode;
            
            // console.log("key: "+key);
            
            switch (key) {            
                case $.fn.tokentextbox.KEY.RETURN:
                case $.fn.tokentextbox.KEY.BACKSPACE:
                case $.fn.tokentextbox.KEY.LEFT:
                    if (tb.gettextselection().start==0) {
                        tb.val("");
                        tb.prev().click();
                        setTimeout(hide_token_textbox, 2);
                    }
                break;
                
                //Did we just type a separator character? If so, then parse the token.
                case $.fn.tokentextbox.KEY.DEL:
                case $.fn.tokentextbox.KEY.RIGHT:
                    if (tb.gettextselection().end>=tb.val().length) add_token(tb);
                break;
                case $.fn.tokentextbox.KEY.TAB:
                    if (event.shiftKey) {
                        original_textbox.attr('backtab', true);
                       
                    }
                break;                  
                default:
                    if (event.charCode == options.separators.charCodeAt(0)) add_token(tb);
                break;
            };
        };
        
        var token_textbox_keyup = function(event) {
            var tb = token_textbox;
            
            //Catch separation character press ending up in a new box
            if (tb.val()==options.separators) tb.val("");                     

            //Copy the text into the text measuring div, then resize the box.
            var gm = $("#_growmeasure");
            gm.css({
                "font-family": tb.css("font-family"),
                "font-size": tb.css("font-size")
            });
            gm.html(tb.val());
            tb.width(gm.width()+options.token_textbox_pad);
 
            var key = event.keyCode;
            switch (key) {                
                case $.fn.tokentextbox.KEY.ESC:
                    tb.val("");
                    return false;
                break;
            };


        };

        var token_textbox_handle_blur = function(event) {
            if(token_textbox.attr("noblur")!="true") {
                token_textbox.unbind();
                add_token(token_textbox);
                token_textbox.css("display", "none");
            };
        };
        
        var add_token = function(token_textbox) { //Converts the contents of token_textbox into a new token, and puts the token before the textbox. Finally, clears the textbox.
            var token = token_textbox.val();
            token_textbox.val("");
            if ($.fn.tokentextbox.token_is_valid(token)) {
                var new_token = $(token_html(token));
                token_textbox.before(new_token);
                new_token.bind("click",token_select);
                new_token.find(".x").click(remove_token_button_click);     
                refresh_original_textbox();
                put_textbox_after(new_token);
            } else {
                var n = token_textbox.next();
                if (n.length>0) {
                    n.click();
                    token_textbox.css("display", "none");
                };
            }
        }
        
        //Overwrite the original textbox contents with all the tokens
        var refresh_original_textbox = function() {
            var newstring = "";
            fake_textbox.find(".token").each(function() {
                var token = $(this).text();
                token = token.substr(0, token.length-1); //Lose the 'x' from the x button
                newstring = newstring + options.separators + token;
            });
            
            newstring = newstring.substr(1, newstring.length);           
            original_textbox.val(newstring);          
        }
        
        var remove_token_button_click = function() {
           var token = $(this).parent();
           token.remove();
           refresh_original_textbox();
        }
        
        var temporary_textbox_keypress = function(event) {
            var key = event.charCode || event.keyCode || 0;
            var selected = fake_textbox.find(".token.selected");
            if (selected.length > 0) {
                switch (key) {
                    case $.fn.tokentextbox.KEY.BACKSPACE:
                        var p = selected.parent();
                        selected.remove();
                        refresh_original_textbox();
                        setTimeout(show_token_textbox,2);
                    break;
                    case $.fn.tokentextbox.KEY.LEFT:
                        selected.removeClass("selected");
                        put_textbox_before(selected);
                    break;
                    case $.fn.tokentextbox.KEY.RIGHT:
                        selected.removeClass("selected");
                        put_textbox_after(selected);                     
                    break;
                }
                event.stopPropagation();
                return false;
            }            
        }
        
        var token_select = function(event) {
            $(".token").removeClass("selected"); //unselect all other tokens.
            selected_token = $(this);
            selected_token.addClass("selected");
            $("#_temporary_text_input").focus(); //this will also process any tokens we are midway through
            event.stopPropagation();
        }

        //Private variables
        var token_textbox;
        var selected_token;
 
        //By convention, we make a private that parameter. This is used to make the object available to the private methods. This is a workaround for an error in the ECMAScript Language Specification which causes this to be set incorrectly for inner functions.
        var that = this;
        
     	var pos = original_textbox.position();
        var fake_textbox = newfaketextbox(original_textbox, pos.left, pos.top);
 
        original_textbox.wrap($.fn.tokentextbox.TAB_STOP_WRAPPER); //Hide the original text box offscreen

        original_textbox.parent().after(fake_textbox); //Add the new simulated textbox in the gap left over
        
        // Put tokens in the new simulated textbox. The ^_^ keeps the box's height even with no tokens.
        fake_textbox.html('<span style="display: block; width:0; overflow:hidden;">^_^</span>'+tokens_html()); 

        $(".token").bind("click",token_select);
        $(".token .x").click(remove_token_button_click);

        //Add a handler for clicking the textbox
        fake_textbox.bind("click", put_textbox_at_end);
        
        //Add a handler for tabbing to the original textbox
        original_textbox.bind('focus', function(e) {
            if ($(this).attr("backtab")) {
                $(this).removeAttr("backtab");
                $(this).movePrev();
            } else {
                fake_textbox.click();
            }
        });

        //Add a handler for keypresses when a token is selected
        $("#_temporary_text_input").bind("keydown", temporary_textbox_keypress);


    };
        
    //'Static' functions (can be overridden by the user - but we also want a callback TODO)
    $.fn.tokentextbox.token_is_valid = function(token) {
       token=$.trim(token);
       if (token.length > 0) {
           if (token[0]=="x") { //DEBUG - nothing beginning with x
               return false;
           } else {
               return true;
           }
       } else {
           return false;
       }
    };

    // Global constants
    $.fn.tokentextbox.TAB_STOP_WRAPPER = $("<div class=\"tab_stop\" style=\"position:absolute; left:-5000px;\"></div>"); 
    $.fn.tokentextbox.KEY = {
       UP: 38,
       DOWN: 40,
       RIGHT: 39,
       LEFT: 37,
       DEL: 46,
       TAB: 9,
       RETURN: 13,
       ESC: 27,
       PAGEUP: 33,
       PAGEDOWN: 34,
       BACKSPACE: 8
    };   

    //Default options
    $.fn.tokentextbox.defaults = {
        separators: ",",
        token_textbox_pad: 25,
        autocomplete_options: {}
    };
    
// end of closure
})(jQuery);